
package com.itsronald.widget;


import com.itsronald.widget.utils.AttrUtils;
import com.itsronald.widget.utils.Utils;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.PageSlider;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;

import java.text.DecimalFormat;
import java.util.Arrays;


public class ViewPagerIndicator extends Component
        implements PageSlider.PageChangedListener,
        Component.DrawTask, Component.EstimateSizeListener {

    // defaults
    private static final int DEFAULT_DOT_SIZE = 40;                      // dp
    private static final int DEFAULT_GAP = 12;                          // dp
    private static final int DEFAULT_ANIM_DURATION = 420;               // ms
    private static final int DEFAULT_UNSELECTED_COLOUR = 0x80FFFFFF;    // 50% white   0x80ffffff
    private static final int DEFAULT_SELECTED_COLOUR = 0xFFFFFFFF;      // 100% white  0xffffffff

    // constants
    private static final double INVALID_FRACTION = -1F;
    private static final double MINIMAL_REVEAL = 0.01F;

    // configurable attributes
    private int dotDiameter;
    private int gap;
    private long animDuration;
    private int unselectedColour;
    private int selectedColour;

    // derived from attributes
    private double dotRadius;
    private double halfDotRadius;
    private long animHalfDuration;
    private double dotTopY;
    private double dotCenterY;
    private double dotBottomY;

    // ViewPager
    private PageSlider viewPager; //PageSlider

    // state
    private int pageCount;
    private int currentPage;
    private int previousPage;
    private double selectedDotX;
    private boolean selectedDotInPosition;
    private double[] dotCenterX;
    private double[] joiningFractions;
    private double retreatingJoinX1;
    private double retreatingJoinX2;
    private double[] dotRevealFractions;
    private boolean isAttachedToWindow;
    private boolean pageChanging;

    // drawing
    private final Paint unselectedPaint;
    private final Paint selectedPaint;
    private Path combinedUnselectedPath;
    private final Path unselectedDotPath;
    private final Path unselectedDotLeftPath;
    private final Path unselectedDotRightPath;
    private final RectFloat rectF; //RectFloat

    // animation
    private AnimatorValue moveAnimation; //AnimatorValue
    private AnimatorGroup joiningAnimationSet; //AnimatorGroup
    private PendingRetreatAnimator retreatAnimation; //撤退动画
    private PendingRevealAnimator[] revealAnimations; //显示点动画
//    private final Interpolator interpolator;//

    public ViewPagerIndicator(final Context context) {
        super(context);
        init();


        unselectedPaint = new Paint();
        unselectedPaint.setAntiAlias(true);
        unselectedPaint.setColor(new Color(unselectedColour));

        selectedPaint = new Paint();
        selectedPaint.setAntiAlias(true);
        selectedPaint.setColor(new Color(selectedColour));

        combinedUnselectedPath = new Path();
        unselectedDotPath = new Path();
        unselectedDotLeftPath = new Path();
        unselectedDotRightPath = new Path();
        rectF = new RectFloat();

        setEstimateSizeListener(this);

        setBindStateChangedListener(new BindStateChangedListener() {
            @Override
            public void onComponentBoundToWindow(Component component) {
                isAttachedToWindow = true;
                addDrawTask(ViewPagerIndicator.this);
            }

            @Override
            public void onComponentUnboundFromWindow(Component component) {
                isAttachedToWindow = false;

            }
        });

    }

    private void init() {
        dotDiameter = DEFAULT_DOT_SIZE;
        dotRadius = dotDiameter / (double) 2;
        halfDotRadius = dotRadius / (double) 2;
        gap = DEFAULT_GAP;

        animDuration = DEFAULT_ANIM_DURATION;
        animHalfDuration = animDuration / 2;
        unselectedColour = DEFAULT_UNSELECTED_COLOUR;
        selectedColour = DEFAULT_SELECTED_COLOUR;

    }

    /**
     * setDotPadding
     *
     * @param newDotPadding 圆点间距
     */
    public void setDotPadding(int newDotPadding) {
        if (gap == newDotPadding) return;
        if (newDotPadding < 0) newDotPadding = 0;
        gap = newDotPadding;
        invalidate();
    }

    /**
     * getDotPadding
     *
     * @return 圆点间距
     */
    public int getDotPadding() {
        return gap;
    }

    /**
     * setDotRadius
     *
     * @param newRadius 新圆点半径
     */
    public void setDotRadius(int newRadius) {
        if (dotRadius == newRadius) return;
        if (newRadius < 0) newRadius = 0;
        dotRadius = newRadius;
        invalidate();
    }

    /**
     * getDotRadius
     *
     * @return 新圆点半径
     */
    public double getDotRadius() {
        return dotRadius;
    }

    /**
     * setUnselectedDotColor  圆点颜色
     *
     * @param color 圆点颜色
     */
    public void setUnselectedDotColor(int color) {
        unselectedColour = color;
        unselectedPaint.setColor(new Color(unselectedColour));
        invalidate();
    }

    /**
     * getUnselectedDotColor  圆点颜色
     *
     * @return 圆点颜色
     */
    public int getUnselectedDotColor() {
        return unselectedColour;
    }


    /**
     * getSelectedDotColor  选中圆点颜色
     *
     * @param color 颜色值
     * @return 选中圆点颜色
     */
    public int getSelectedDotColor(int color) {
        return selectedColour;
    }

    /**
     * setSelectedDotColor
     *
     * @param color 选中圆点颜色
     */
    public void setSelectedDotColor(int color) {
        selectedColour = color;
        selectedPaint.setColor(new Color(selectedColour));
        invalidate();
    }

    public ViewPagerIndicator(Context context, AttrSet attrs) {
        super(context, attrs);

        dotDiameter = AttrUtils.getInteger(context, attrs, "ipi_dotDiameter", DEFAULT_DOT_SIZE);
        dotRadius = dotDiameter / (double) 2;
        halfDotRadius = dotRadius / 2;
        gap = AttrUtils.getInteger(context, attrs, "ipi_dotGap", DEFAULT_GAP);

        animDuration = (long) AttrUtils.getInteger(context, attrs, "ipi_animationDuration", DEFAULT_ANIM_DURATION);
        animHalfDuration = animDuration / 2;
        unselectedColour = AttrUtils.getColor(attrs, "ipi_pageIndicatorColor", DEFAULT_UNSELECTED_COLOUR);
        selectedColour = AttrUtils.getColor(attrs, "ipi_currentPageIndicatorColor", DEFAULT_SELECTED_COLOUR);

        unselectedPaint = new Paint();
        unselectedPaint.setAntiAlias(true);
        unselectedPaint.setColor(new Color(unselectedColour));

        selectedPaint = new Paint();
        selectedPaint.setAntiAlias(true);
        selectedPaint.setColor(new Color(selectedColour));

        combinedUnselectedPath = new Path();
        unselectedDotPath = new Path();
        unselectedDotLeftPath = new Path();
        unselectedDotRightPath = new Path();
        rectF = new RectFloat();


        setEstimateSizeListener(this);

        setBindStateChangedListener(new BindStateChangedListener() {
            @Override
            public void onComponentBoundToWindow(Component component) {
                isAttachedToWindow = true;
                addDrawTask(ViewPagerIndicator.this);
            }

            @Override
            public void onComponentUnboundFromWindow(Component component) {
                isAttachedToWindow = false;

            }
        });

    }

    /**
     * PageSlider
     *
     * @param viewPager PageSlider
     */
    public void setViewPager(PageSlider viewPager) {
        this.viewPager = viewPager;
        viewPager.addPageChangedListener(this);
        setPageCount(viewPager.getProvider().getCount());
        setCurrentPageImmediate();
    }


    DecimalFormat df = new DecimalFormat("0.00");

    private double positionOffset;
    private double positionOffsetPixels;

    @Override
    public void onPageSliding(final int position, final float positionOffset, final int positionOffsetPixels) {
        //positionOffsetPixels  从0->1  为正数   1->0  为负数  API5 返回像素时后阶段会返回相反数据
        this.positionOffset = positionOffset;
        this.positionOffsetPixels = positionOffsetPixels;

    }

    @Override
    public void onPageChosen(int position) {

        //滑动状态 为true表示已选中   为兼容positionOffsetPixels bug
        boolean isCheckStatus = true;
        if (isAttachedToWindow) {
            // this is the main event we're interested in!
            setSelectedPage(position);
        } else {
            // when not attached, don't animate the move, just store immediately
            setCurrentPageImmediate();
        }

        positionOffset = Float.parseFloat(df.format(positionOffset));
        if (isAttachedToWindow) {
            double fraction = 0F;
            if (positionOffsetPixels > 0) {
                fraction = Utils.floatToSubtract(1F, 0);//向右划  上一页
                position = Math.max(position - 1, 0);
            } else {
                fraction = Utils.floatToSubtract(1F, 1F);//向右划  上一页
                position = Math.max(position, 0);
            }

            int currentPosition = pageChanging ? previousPage : currentPage;
            // when swiping from #2 to #1 ViewPager reports position as 1 and a descending offset
            //当从#2滑动到#1时，ViewPager报告位置为1和递减偏移量
            // need to convert this into our left-dot-based 'coordinate space'
            //需要将其转换为基于左点的“坐标空间”
            int leftDotPosition = position;

            if (currentPosition != position) {
//                fraction = 1f - positionOffset;//float直接计算会有问题
                //是否滑动到下一页
                fraction = 1F;
                // if user scrolls completely to next page then the position param updates to that
                //如果用户完全滚动到下一页，那么位置参数将更新到该页
                // new page but we're not ready to switch our 'current' page yet so adjust for that
                //新的页面，但我们还没有准备好切换我们的'当前'页，所以调整
                leftDotPosition = Math.min(currentPosition, position);
            }
            setJoiningFraction(leftDotPosition, fraction);
        }
    }

    @Override
    public void onPageSlideStateChanged(final int position) {
        // nothing to do

    }


    private void setPageCount(final int pages) {
        pageCount = pages;
        resetState();
        postLayout();
    }

    //计算出的位置
    private void calculateDotPositions(final int width, final int height) {
        int left = getPaddingLeft();
        int top = getPaddingTop();
        int right = width - getPaddingRight();

        int requiredWidth = getRequiredWidth();
        double startLeft = left + ((right - left - requiredWidth) / 2) + dotRadius;

        dotCenterX = new double[pageCount];
        for (int i = 0; i < pageCount; i++) {
            dotCenterX[i] = startLeft + i * (dotDiameter + gap);
        }
        dotTopY = top;
        dotCenterY = top + dotRadius;
        dotBottomY = top + dotDiameter;

        //设置当前页
        setCurrentPageImmediate();
    }

    private void setCurrentPageImmediate() {
        if (viewPager != null) {
            currentPage = viewPager.getCurrentPage();//.getCurrentItem();
        } else {
            currentPage = 0;
        }
        if (dotCenterX != null && dotCenterX.length > 0 && (moveAnimation == null || !moveAnimation.isRunning())) {
            selectedDotX = dotCenterX[currentPage];
        }
    }

    private void resetState() {
        joiningFractions = new double[pageCount == 0 ? 0 : (pageCount - 1)];
        Arrays.fill(joiningFractions, 0F);
        dotRevealFractions = new double[pageCount];
        Arrays.fill(dotRevealFractions, 0F);
        retreatingJoinX1 = INVALID_FRACTION;
        retreatingJoinX2 = INVALID_FRACTION;
        selectedDotInPosition = true;
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int desiredHeight = getDesiredHeight();
        int height;
        switch (EstimateSpec.getMode(heightMeasureSpec)) {
            case EstimateSpec.PRECISE://EXACTLY
                height = EstimateSpec.getSize(heightMeasureSpec);
                break;
            case EstimateSpec.NOT_EXCEED://AT_MOST
                height = Math.min(desiredHeight, EstimateSpec.getSize(heightMeasureSpec));
                break;
            default: // MeasureSpec.UNSPECIFIED
                height = desiredHeight;
                break;
        }

        int desiredWidth = getDesiredWidth();
        int width;
        switch (EstimateSpec.getMode(widthMeasureSpec)) {
            case EstimateSpec.PRECISE://EXACTLY
                width = EstimateSpec.getSize(widthMeasureSpec);
                break;
            case EstimateSpec.NOT_EXCEED://AT_MOST
                width = Math.min(desiredWidth, EstimateSpec.getSize(widthMeasureSpec));
                break;
            default: // MeasureSpec.UNSPECIFIED
                width = desiredWidth;
                break;
        }
        setEstimatedSize(width, height);
        calculateDotPositions(width, height);

        return false;
    }


    private int getDesiredHeight() {
        return getPaddingTop() + dotDiameter + getPaddingBottom();
    }

    private int getRequiredWidth() {
        return pageCount * dotDiameter + (pageCount - 1) * gap;
    }

    private int getDesiredWidth() {
        return getPaddingLeft() + getRequiredWidth() + getPaddingRight();
    }

    @Override
    public void addDrawTask(DrawTask task) {
        super.addDrawTask(task);
        task.onDraw(this, mCanvasForTaskOverContent);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (viewPager == null || pageCount == 0) return;

        drawUnselected(canvas);

        drawSelected(canvas);

    }

    private void drawUnselected(Canvas canvas) {
        combinedUnselectedPath.rewind();
        // draw any settled, revealing or joining dots  画出任何固定的、暴露的或连接的点
        for (int page = 0; page < pageCount; page++) {
            int nextXIndex = page == pageCount - 1 ? page : page + 1;
            if (dotCenterX == null) return;
            Path unselectedPath = getUnselectedPath(page, dotCenterX[page], dotCenterX[nextXIndex], page == pageCount - 1 ? INVALID_FRACTION : joiningFractions[page], dotRevealFractions[page]);
            unselectedPath.addPath(combinedUnselectedPath);
            combinedUnselectedPath.addPath(unselectedPath);
        }
        // draw any retreating joins  绘制后退连接
        if (retreatingJoinX1 != INVALID_FRACTION) {
            Path retreatingJoinPath = getRetreatingJoinPath();
            combinedUnselectedPath.addPath(retreatingJoinPath);
        }

        canvas.drawPath(combinedUnselectedPath, unselectedPaint);
    }

    private Path getUnselectedPath(int page, double centerX, double nextCenterX, double joiningFraction, double dotRevealFraction) {
        unselectedDotPath.rewind();

        if ((joiningFraction == 0F || joiningFraction == INVALID_FRACTION)//浮点数运算问题
                && dotRevealFraction == 0F && !(page == currentPage && selectedDotInPosition == true)) {
//            LogUtil.loge("==getUnselectedPath====dotCenterX["+page+"]="+dotCenterX[page]+"=dotCenterY="+dotCenterY+"=dotRadius="+dotRadius);

            // case #1 – At rest  画未选中圆
            unselectedDotPath.addCircle((float) dotCenterX[page], (float) dotCenterY, (float) dotRadius, Path.Direction.CLOCK_WISE);//CW
        }
        //相吸动画
        double endX2;
        double endY1;
        double endY2;
        // working values for beziers
        double endX1;
        double controlX1;
        double controlY1;
        double controlX2;
        double controlY2;
        if (joiningFraction > 0F && joiningFraction <= 0.5F && retreatingJoinX1 == INVALID_FRACTION) {

            // case #2 – Joining neighbour, still separate

            // start with the left dot
            unselectedDotLeftPath.rewind();

            // start at the bottom center  从底部中心开始
            unselectedDotLeftPath.moveTo((float) centerX, (float) dotBottomY);

            // semi circle to the top center  半圆到上止点
            rectF.clear();
            rectF.fuse((float) (centerX - dotRadius), (float) dotTopY, (float) (centerX + dotRadius), (float) dotBottomY);
            unselectedDotLeftPath.arcTo(rectF, 90, 180, true);

            // cubic to the right middle  到右中间
            endX1 = centerX + dotRadius + (joiningFraction * gap);
            endY1 = dotCenterY;
            controlX1 = centerX + halfDotRadius;
            controlY1 = dotTopY;
            controlX2 = endX1;
            controlY2 = endY1 - halfDotRadius;

            Point point1 = new Point((float) controlX1, (float) controlY1);
            Point point2 = new Point((float) controlX2, (float) controlY2);
            Point point3 = new Point((float) endX1, (float) endY1);
            unselectedDotLeftPath.cubicTo(point1, point2, point3);

            // cubic back to the bottom center   回到底部中心
            endX2 = centerX;
            endY2 = dotBottomY;
            controlX1 = endX1;
            controlY1 = endY1 + halfDotRadius;
            controlX2 = centerX + halfDotRadius;
            controlY2 = dotBottomY;

            Point point11 = new Point((float) controlX1, (float) controlY1);
            Point point12 = new Point((float) controlX2, (float) controlY2);
            Point point13 = new Point((float) endX2, (float) endY2);
            unselectedDotLeftPath.cubicTo(point11, point12, point13);

            unselectedDotPath.addPath(unselectedDotLeftPath);

            // now do the next dot to the right  现在做右边的下一个点
            unselectedDotRightPath.rewind();

            // start at the bottom center 从底部中心开始
            unselectedDotRightPath.moveTo((float) nextCenterX, (float) dotBottomY);

            // semi circle to the top center  半圆到上止点
            rectF.clear();
            rectF.fuse((float) (nextCenterX - dotRadius), (float) dotTopY, (float) (nextCenterX + dotRadius), (float) dotBottomY);
            unselectedDotRightPath.arcTo(rectF, 90, -180, true);

            // cubic to the left middle   到左中间
            endX1 = nextCenterX - dotRadius - (joiningFraction * gap);
            endY1 = dotCenterY;
            controlX1 = nextCenterX - halfDotRadius;
            controlY1 = dotTopY;
            controlX2 = endX1;
            controlY2 = endY1 - halfDotRadius;

            Point point21 = new Point((float) controlX1, (float) controlY1);
            Point point22 = new Point((float) controlX2, (float) controlY2);
            Point point23 = new Point((float) endX1, (float) endY1);
            unselectedDotRightPath.cubicTo(point21, point22, point23);

            // cubic back to the bottom center  立方回到底部中心
            endX2 = nextCenterX;
            endY2 = dotBottomY;
            controlX1 = endX1;
            controlY1 = endY1 + halfDotRadius;
            controlX2 = endX2 - halfDotRadius;
            controlY2 = dotBottomY;

            Point point31 = new Point((float) controlX1, (float) controlY1);
            Point point32 = new Point((float) controlX2, (float) controlY2);
            Point point33 = new Point((float) endX2, (float) endY2);
            unselectedDotRightPath.cubicTo(point31, point32, point33);
            unselectedDotPath.addPath(unselectedDotRightPath);
        }
        //相连之后的动画
        if (joiningFraction > 0.5F && joiningFraction < 1F && retreatingJoinX1 == INVALID_FRACTION) {

            // case #3 – Joining neighbour, combined curved    案例3–连接相邻，组合曲线

            // adjust the fraction so that it goes from 0.3 -> 1 to produce a more realistic 'join'
            double adjustedFraction = (joiningFraction - 0.2F) * 1.25F;

            // start in the bottom left
            unselectedDotPath.moveTo((float) centerX, (float) dotBottomY);

            // semi-circle to the top left  左上半圆
            rectF.clear();
            rectF.fuse((float) (centerX - dotRadius), (float) dotTopY, (float) (centerX + dotRadius), (float) dotBottomY);
            unselectedDotPath.arcTo(rectF, 90, 180, true);

            // bezier to the middle top of the join  贝塞尔到中间顶端的连接
            endX1 = centerX + dotRadius + (gap / (double) 2);
            endY1 = dotCenterY - (adjustedFraction * dotRadius);
            controlX1 = endX1 - (adjustedFraction * dotRadius);
            controlY1 = dotTopY;
            controlX2 = endX1 - ((1 - adjustedFraction) * dotRadius);
            controlY2 = endY1;

            Point point14 = new Point((float) controlX1, (float) controlY1);
            Point point24 = new Point((float) controlX2, (float) controlY2);
            Point point34 = new Point((float) endX1, (float) endY1);
            unselectedDotPath.cubicTo(point14, point24, point34);

            // bezier to the top right of the join  连接右上角的bezier
            endX2 = nextCenterX;
            endY2 = dotTopY;
            controlX1 = endX1 + ((1 - adjustedFraction) * dotRadius);
            controlY1 = endY1;
            controlX2 = endX1 + (adjustedFraction * dotRadius);
            controlY2 = dotTopY;

            Point point15 = new Point((float) controlX1, (float) controlY1);
            Point point25 = new Point((float) controlX2, (float) controlY2);
            Point point35 = new Point((float) endX2, (float) endY2);

            unselectedDotPath.cubicTo(point15, point25, point35);

            // semi-circle to the bottom right  右下半圆
            rectF.clear();
            rectF.fuse((float) (nextCenterX - dotRadius), (float) dotTopY, (float) (nextCenterX + dotRadius), (float) dotBottomY);
            unselectedDotPath.arcTo(rectF, 270, 180, true);

            // bezier to the middle bottom of the join  贝塞尔到中间底部的连接
            // endX1 stays the same  endX1保持不变
            endY1 = dotCenterY + (adjustedFraction * dotRadius);
            controlX1 = endX1 + (adjustedFraction * dotRadius);
            controlY1 = dotBottomY;
            controlX2 = endX1 + ((1 - adjustedFraction) * dotRadius);
            controlY2 = endY1;

            Point point16 = new Point((float) controlX1, (float) controlY1);
            Point point26 = new Point((float) controlX2, (float) controlY2);
            Point point36 = new Point((float) endX1, (float) endY1);
            unselectedDotPath.cubicTo(point16, point26, point36);

            // bezier back to the start point in the bottom left  贝塞尔回到左下角的起点
            endX2 = centerX;
            endY2 = dotBottomY;
            controlX1 = endX1 - ((1 - adjustedFraction) * dotRadius);
            controlY1 = endY1;
            controlX2 = endX1 - (adjustedFraction * dotRadius);
            controlY2 = endY2;

            Point point17 = new Point((float) controlX1, (float) controlY1);
            Point point27 = new Point((float) controlX2, (float) controlY2);
            Point point37 = new Point((float) endX2, (float) endY2);
            unselectedDotPath.cubicTo(point17, point27, point37);
        }

        if (joiningFraction == 1 && retreatingJoinX1 == INVALID_FRACTION) {

            // case #4 Joining neighbour, combined straight technically we could use case 3 for this
            // situation as well but assume that this is an optimization rather than faffing around
            // with beziers just to draw a rounded rect  用贝塞尔曲线画一个圆形的矩形  最后连接的矩形
            rectF.clear();
            rectF.fuse((float) (centerX - dotRadius), (float) dotTopY, (float) (nextCenterX + dotRadius), (float) dotBottomY);
            unselectedDotPath.addRoundRect(rectF, (float) dotRadius, (float) dotRadius, Path.Direction.CLOCK_WISE);//CW
        }

        // case #5 is handled by #getRetreatingJoinPath()
        // this is done separately so that we can have a single retreating path spanning  这是分开做的，这样我们就可以有一个单一的撤退路径
        // multiple dots and therefore animate it's movement smoothly  多个点，因此它的运动动画顺利

        if (dotRevealFraction > MINIMAL_REVEAL) {
            // case #6 – previously hidden dot revealing  以前隐藏的点显示动画
            unselectedDotPath.addCircle((float) centerX, (float) dotCenterY, (float) (dotRevealFraction * dotRadius), Path.Direction.CLOCK_WISE); //CW
        }

        return unselectedDotPath;
    }

    //撤退路径
    private Path getRetreatingJoinPath() {
        unselectedDotPath.rewind();
        rectF.clear();
        rectF.fuse((float) retreatingJoinX1, (float) dotTopY, (float) retreatingJoinX2, (float) dotBottomY);
        unselectedDotPath.addRoundRect(rectF, (float) dotRadius, (float) dotRadius, Path.Direction.CLOCK_WISE); //CW
        return unselectedDotPath;
    }

    private void drawSelected(Canvas canvas) {
        canvas.drawCircle((float) selectedDotX, (float) dotCenterY, (float) dotRadius, selectedPaint);
    }

    //选中页面后开始准备其他动画
    private void setSelectedPage(int now) {
        if (now == currentPage || dotCenterX == null || dotCenterX.length <= now) return;
        pageChanging = true;
        previousPage = currentPage;
        currentPage = now;
        final int steps = Math.abs(now - previousPage);

        if (steps > 1) {
            if (now > previousPage) {
                for (int i = 0; i < steps; i++) {
                    setJoiningFraction(previousPage + i, 1F);
                }
            } else {
                for (int i = -1; i > -steps; i--) {
                    setJoiningFraction(previousPage + i, 1F);
                }
            }
        }
        // create the anim to move the selected dot – this animator will kick off
        // retreat animations when it has moved 75% of the way.
        // The retreat animation in turn will kick of reveal anims when the
        // retreat has passed any dots to be revealed
        //创建动画来移动选定的点-这个动画师将开始
        //当它移动75%的时候撤退。
        //撤退动画反过来会在
        //撤退已经过了任何需要揭露的点

        moveAnimation = createMoveSelectedAnimator(dotCenterX[now], previousPage, now, steps);
        moveAnimation.setCurveType(Animator.CurveType.LINEAR);//LINEAR
        moveAnimation.start();

    }

    //选中后创建移动动画
    private AnimatorValue createMoveSelectedAnimator(final double moveTo, int was, int now, int steps) {

        // create the actual move animator
//        AnimatorValue moveSelected = AnimatorValue.ofFloat(selectedDotX, moveTo);
        AnimatorValue moveSelected = new AnimatorValue();
// also set up a pending retreat anim – this starts when the move is 75% complete
        //等待撤退动画  超过75%就开始执行
        retreatAnimation = new PendingRetreatAnimator(was, now, steps, now > was ? new RightwardStartPredicate(moveTo - ((moveTo - selectedDotX) * 0.25F)) ://0.25f
                new LeftwardStartPredicate(moveTo + ((selectedDotX - moveTo) * 0.25F)));//0.25f

        retreatAnimation.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                resetState();
                pageChanging = false;

            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        });

        moveSelected.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                selectedDotX = selectedDotX + (moveTo - selectedDotX) * v;
                //移动距离到达开始执行点的显示动画
                retreatAnimation.startIfNecessary(selectedDotX);
                invalidate();
            }
        });

        moveSelected.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
                selectedDotInPosition = false;
            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                selectedDotInPosition = true;

            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        });


        // slightly delay the start to give the joins a chance to run
        // unless dot isn't in position yet – then don't delay!
        moveSelected.setDelay(selectedDotInPosition ? animDuration / 4L : 0L);

        moveSelected.setDuration(animDuration * 3L / 4L);
//        moveSelected.setInterpolator(interpolator);
        moveSelected.setCurveType(Animator.CurveType.LINEAR);//LINEAR

        return moveSelected;
    }

    //设置每个点的动画百分比
    private void setJoiningFraction(int leftDot, double fraction) {
        if (leftDot < joiningFractions.length) {
            joiningFractions[leftDot] = fraction;
            invalidate();
        }
    }

    private void clearJoiningFractions() {
        Arrays.fill(joiningFractions, 0F);
        invalidate();
    }

    private void setDotRevealFraction(int dot, double fraction) {
        if (dot < dotRevealFractions.length) {
            dotRevealFractions[dot] = fraction;
        }
        invalidate();
    }

    private void cancelJoiningAnimations() {
        if (joiningAnimationSet != null && joiningAnimationSet.isRunning()) {
            joiningAnimationSet.cancel();
        }
    }

    /**
     * A {@link AnimatorValue } that starts once a given predicate returns true.
     */
    public abstract class PendingStartAnimator extends AnimatorValue {//ValueAnimator

        protected boolean hasStarted;
        protected StartPredicate predicate;

        public PendingStartAnimator(StartPredicate predicate) {
            super();
            this.predicate = predicate;
            hasStarted = false;
        }

        //设置为true就开始动画
        public void startIfNecessary(double currentValue) {
            //当前值是否大于初始值大于就开始执行动画
            if (!hasStarted && predicate.shouldStart(currentValue)) {
                start();
                hasStarted = true;
            }
        }
    }

    /**
     * An Animator that shows and then shrinks a retreating join between the previous and newly
     * 显示然后收缩上一个和新一个之间的后退连接的动画师
     * selected pages.  This also sets up some pending dot reveals – to be started when the retreat
     * 选定的页面。这也设置了一些待处理的点显示-在撤退时开始
     * has passed the dot to be revealed.
     */
    public class PendingRetreatAnimator extends PendingStartAnimator {

        public PendingRetreatAnimator(int was, int now, int steps, StartPredicate predicate) {
            super(predicate);
            setDuration(animHalfDuration);//setDurationInternal
//            setInterpolator(interpolator);//无
            setCurveType(CurveType.LINEAR);//LINEAR

            // work out the start/end values of the retreating join from the direction we're
            // travelling in.  Also look at the current selected dot position, i.e. we're moving on
            // before a prior anim has finished.
            final double initialX1 = now > was ? Math.min(dotCenterX[was], selectedDotX) - dotRadius : dotCenterX[now] - dotRadius;
//            final double finalX1 = now > was ? dotCenterX[now] - dotRadius : dotCenterX[now] - dotRadius;
            final double finalX1 = dotCenterX[now] - dotRadius;
            final double initialX2 = now > was ? dotCenterX[now] + dotRadius : Math.max(dotCenterX[was], selectedDotX) + dotRadius;
//            final double finalX2 = now > was ? dotCenterX[now] + dotRadius : dotCenterX[now] + dotRadius;
//            final double finalX2 = dotCenterX[now] + dotRadius;

            //显示点动画
            revealAnimations = new PendingRevealAnimator[steps];
            // hold on to the indexes of the dots that will be hidden by the retreat so that
            // we can initialize their revealFraction's i.e. make sure they're hidden while the
            // reveal animation runs
            final int[] dotsToHide = new int[steps];
            if (initialX1 != finalX1) { // rightward retreat
//                setFloatValues(initialX1, finalX1);//未移植

                // create the reveal animations that will run when the retreat passes them
                //创建将在撤退经过时运行的显示动画
                for (int i = 0; i < steps; i++) {
                    revealAnimations[i] = new PendingRevealAnimator(was + i, new RightwardStartPredicate(dotCenterX[was + i]));
                    dotsToHide[i] = was + i;
                }

                //setValueUpdateListener(AnimatorValue.ValueUpdateListener)
                setValueUpdateListener(new ValueUpdateListener() {
                    @Override
                    public void onUpdate(AnimatorValue valueAnimator, float v) {
                        // todo avoid autoboxing
//                        retreatingJoinX1 = (Float) valueAnimator.getAnimatedValue();

                        retreatingJoinX1 = initialX1 + (finalX1 - initialX1) * v;
//                        LogUtil.loge("显示点动画==retreatingJoinX1="+retreatingJoinX1+"==v="+v);
                        invalidate();
                        // start any reveal animations if we've passed them
                        for (PendingRevealAnimator pendingReveal : revealAnimations) {
                            pendingReveal.startIfNecessary(retreatingJoinX1);
                        }

                    }
                });
            } else { // (initialX2 != finalX2) leftward retreat
//                setFloatValues(initialX2, finalX2);//未移植

                // create the reveal animations that will run when the retreat passes them
                ////创建将在撤退经过时运行的显示动画
                for (int i = 0; i < steps; i++) {
                    revealAnimations[i] = new PendingRevealAnimator(was - i, new LeftwardStartPredicate(dotCenterX[was - i]));
                    dotsToHide[i] = was - i;
                }
                setValueUpdateListener(new ValueUpdateListener() {
                    @Override
                    public void onUpdate(AnimatorValue valueAnimator, float v) {
                        retreatingJoinX2 = initialX2 + (finalX1 - initialX2) * v;
                        invalidate();

                        // start any reveal animations if we've passed them
                        for (PendingRevealAnimator pendingReveal : revealAnimations) {
                            pendingReveal.startIfNecessary(retreatingJoinX2);
                        }

                    }
                });
            }

            setStateChangedListener(new StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                    cancelJoiningAnimations();
                    clearJoiningFractions();
                    for (int dot : dotsToHide) {
                        setDotRevealFraction(dot, MINIMAL_REVEAL);
                    }
                    retreatingJoinX1 = initialX1;
                    retreatingJoinX2 = initialX2;
                    invalidate();

                }

                @Override
                public void onStop(Animator animator) {
                }

                @Override
                public void onCancel(Animator animator) {

                }

                @Override
                public void onEnd(Animator animator) {
                    retreatingJoinX1 = INVALID_FRACTION;
                    retreatingJoinX2 = INVALID_FRACTION;
                    invalidate();
                }

                @Override
                public void onPause(Animator animator) {

                }

                @Override
                public void onResume(Animator animator) {

                }
            });
        }
    }

    /**
     * An Animator that animates a given dot's revealFraction i.e. scales it up
     * 最后滑动完成前一个点由小到大显示动画  ok
     */
    public class PendingRevealAnimator extends PendingStartAnimator {

        private int dot;

        public PendingRevealAnimator(int dot, StartPredicate predicate) {
            super(predicate);
            this.dot = dot;
            setDuration(animHalfDuration);
            setCurveType(CurveType.LINEAR);//SMOOTH_STEP  LINEAR

            setValueUpdateListener(new ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float v) {
                    //设置当前显示点的百分比
                    setDotRevealFraction(PendingRevealAnimator.this.dot, v);
                }
            });

            setStateChangedListener(new StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                }

                @Override
                public void onStop(Animator animator) {
                }

                @Override
                public void onCancel(Animator animator) {

                }

                @Override
                public void onEnd(Animator animator) {
//                    动画显示完初始化百分比
                    setDotRevealFraction(PendingRevealAnimator.this.dot, 0F);
                    invalidate();
                }

                @Override
                public void onPause(Animator animator) {

                }

                @Override
                public void onResume(Animator animator) {

                }
            });

        }
    }

    /**
     * A predicate used to start an animation when a test passes
     */
    public abstract class StartPredicate {

        protected double thresholdValue;

        public StartPredicate(double thresholdValue) {
            this.thresholdValue = thresholdValue;
        }

        abstract boolean shouldStart(double currentValue);

    }

    /**
     * A predicate used to start an animation when a given value is greater than a threshold
     */
    public class RightwardStartPredicate extends StartPredicate {

        public RightwardStartPredicate(double thresholdValue) {
            super(thresholdValue);
        }

        boolean shouldStart(double currentValue) {
            return currentValue > thresholdValue;
        }
    }

    /**
     * A predicate used to start an animation then a given value is less than a threshold
     */
    public class LeftwardStartPredicate extends StartPredicate {

        public LeftwardStartPredicate(double thresholdValue) {
            super(thresholdValue);
        }

        boolean shouldStart(double currentValue) {
            return currentValue < thresholdValue;
        }
    }

}
